/*
 * Seth Simon
 * Andrew Pomnean
 * memoryManager.c
 * 
 * Compilation instructions: gcc -std=c99 -o memoryManager memoryManager.c
 *
 * Changes since last revision: Main function is done.
                                                                Initialization function is done.
                                                                Syntax-checking functions have been implemented.
                                                                Everything but allocate(), deallocate(), and printMemoryState() functions are done.
 * 
 */


#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// GLOBAL VARIABLES:
// #################

enum boolean{FALSE,TRUE};

enum POLICY{SEGMENTATION,PAGING};
int policy = -1; // 0/SEGMENTATION is segmentation, 1/PAGING is paging, -1 is if the memory manager has not yet been initialized.

char *error_message; //if a function fails, this will point to the error message
int lineNumber = 0; //line number currently being processed in the input file

struct hole {
        int start;
        int size;
        struct hole *next;
        struct hole *prev;
} hole_list;

struct page {
        int virtual_page; //virtual page number
        int physical_page; //corresponds to this physical page number
        struct page *next;
        int used; //number of bytes of this page that are in use
};

struct process {
        int pid;
        int size[4]; //size[0] is total size, [1] is text size, [2] is data size, [3] is heap size
        struct process *next;
        int start[3]; //for segmentation, starting address of text, data, and heap segments (respectively). For paging, the starting address is stored in start[0]
        struct page *page_map;
} process_list;


// FUNCTION PROTOTYPES:
// ####################

int initialize(int,int); //initialize with (argument 1) bytes of memory, using policy (argument 2 (0 for seg., 1 for paging)).
//Returns 1/TRUE for success, 0/FALSE for failure

int allocate(int,int,int,int,int); // allocate (argument 1) bytes of memory to the process with pid (argument 2), with (argument 3), (argument 4), and (argument 5)
// bytes for the text, data, and heap segments (respectively). returns 1/TRUE for success, 0/FALSE for failure.

int deallocate(int); //deallocate the memory that was allocated to the process with pid (argument 1). returns 1/TRUE for success, 0/FALSE for failure.

void printMemoryState(); //prints current state of memory, as instructed in the project handout.

int checkProgramSyntax(int,char **); //checks syntax of the arguments given to this program. returns FALSE/0 if the syntax is bad, and puts the error message in
// the error_message variable. Otherwise, returns 1/TRUE.

int checkSyntax(char *); //checks the syntax of any line in the input file. Only checks that the number of arguments is correct, and that the arguments that should be
// integers are integers. Returns 0/FALSE and puts an error message in the global variable if the syntax is bad. Returns 1/TRUE is the syntax is correct.

char *trimWhiteSpace(char *); //trim whitespace from the beginning and end of a string, returns the new string

// MAIN FUNCTION:
// ##############

int main(int argc, char **argv)
{
        if(!checkProgramSyntax(argc,argv)) {
                printf("Error: %s\n",error_message);
                exit(1);
        }
        char line[1024]; //stores a line from the input file
        strcpy(line,"");
        
        FILE *input;
        if(!(input = fopen(argv[1],"r"))) {
                printf("Error: Unable to open file %s\n",argv[1]);
                exit(1);
        }
        
        while(fgets(line,1023,input)) { //while not EOF
                lineNumber++;
                if(!checkSyntax(line)) {
                        printf("Error: Line %d: %s\n",lineNumber,error_message);
                        exit(1);
                }
                
                if(isdigit(*line)) { //then this is the initialization statement
                        int arg1 = atoi(trimWhiteSpace(strtok(line," ")));
                        int arg2 = atoi(trimWhiteSpace(strtok(NULL," ")));
                        if(!initialize(arg1,arg2)) {
                                printf("Error: Line %d: %s\n",lineNumber,error_message);
                                exit(1);
                        }
                }
                else if(*line == 'A' || *line == 'a') { //allocation statement
                        int args[5];
                        strtok(line," "); //get rid of the "A"
                        for(int i = 0; i < 5; i++)
                                args[i] = atoi(trimWhiteSpace(strtok(NULL," ")));
                        
                        if(!allocate(args[0],args[1],args[2],args[3],args[4])) {
                                printf("Error: Line %d: %s\n",lineNumber,error_message);
                                exit(1);
                        }
                }
                else if(*line == 'D' || *line == 'd') { //deallocation statement
                        strtok(line," "); //get rid of the "D"
                        int arg = atoi(trimWhiteSpace(strtok(NULL," ")));
                        if(!deallocate(arg)) {
                                printf("Error: Line %d: %s\n",lineNumber,error_message);
                                exit(1);
                        }
                }
                else if(*line=='P' || *line=='p') { //print current memory state.
                        printMemoryState();
                }
                else { //syntax error in input file
                        printf("Error: Line %d: Syntax error\n",lineNumber);
                        exit(1);
                }
        } //end while not EOF
        
        return 0;
} //end main



// USER-DEFINED FUNCTIONS:
// #######################


// Initialize:
// ###########
int initialize(int bytes, int pol) {
        policy = pol;
        if(pol != SEGMENTATION && pol != PAGING) {
                error_message = "Second argument is invalid.";
                return FALSE;
        }
        else if(pol == PAGING && bytes % 32 != 0) {
                error_message = "Physical memory must be a multiple of 32 for paging";
                return FALSE;
        }
        hole_list.start = 0;
        hole_list.size = bytes;
        hole_list.next = NULL;
        hole_list.prev = NULL;
        
        process_list.page_map = NULL;
        
        return TRUE;
}

// Check Program Syntax:
// #####################
int checkProgramSyntax(int argc,char **argv) {
        if(argc != 2) {
                error_message = "Usage: memoryManager (input file)\n";
                return FALSE;
        }
        return TRUE;
}

//Check Syntax:
// ############
int checkSyntax(char *original_line) {
        char line[strlen(original_line) + 1];
        strcpy(line,original_line);
        
        char *arguments[7];
        arguments[0] = trimWhiteSpace(strtok(line," "));
        for(int i = 1; i < 7; i++) {
                if(!(arguments[i] = trimWhiteSpace(strtok(NULL," "))))
                        break;
        }
        
        if(strlen(arguments[0]) != 1 && !isdigit(*arguments[0])) {
                error_message = "First argument is invalid";
                return FALSE;
        }
        
        //for checking that certain arguments are integers:
        char *endp = '\0';
        int required[2]; //required[0] is the first index of the arguments array that must be an integer. required[1] is the last index that must be an integer.
        
        
        //check that number of arguments is correct:
        switch(*arguments[0]) {
                case 'a':
                case 'A':       if(arguments[6] || !arguments[5]) { //if too many or not enough arguments
                                                error_message = "Wrong number of arguments.";
                                                return FALSE;
                                        }
                                        required[0] = 1;
                                        required[1] = 5;
                                        break;
                case 'd':
                case 'D':       if(arguments[2] || !arguments[1]) { //if too many or not enough arguments
                                                error_message = "Wrong number of arguments.";
                                                return FALSE;
                                        }
                                        required[0] = 1;
                                        required[1] = 1;
                                        break;
                case 'p':
                case 'P':       if(arguments[1]) {
                                                error_message = "Too many arguments.";
                                                return FALSE;
                                        }
                                        required[0] = 0;
                                        required[1] = -1;
                                        break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':       if(arguments[2] || !arguments[1]) {
                                                error_message = "Wrong number of arguments.";
                                                return FALSE;
                                        }
                                        required[0] = 0;
                                        required[1] = 1;
                                        break;
                default:        error_message = "First argument is invalid";
                                        return FALSE;
                                        break;
        }
        
        //check that the arguments that should be integers are integers:
        for(int i = required[0]; i < required[1] + 1;i++) {
                int value = (int) strtol(arguments[i],&endp,10);
                if(endp == arguments[i] || *endp || errno == ERANGE) {
                        error_message = "Invalid argument";
                        return FALSE;
                }
        }
        return TRUE;
}

// Trim White Space:
// #################
char *trimWhiteSpace(char *argument) {
        //CASES TO DEAL WITH:
                //1. "        " (all whitespace)
                //2. "    a   " first non-whitespace char = last non-whitespace char
                //3. "  bb    " typical situation
                //4. argument==NULL
                
        if(!argument) //case 4
                return argument;
        char *result = malloc(strlen(argument)+1);
        
        //find first non-whitespace character:
        
        int first = 0; // *(result+0) is first non-whitespace char
        char *ptr = argument;
        while(*ptr && isspace(*ptr))
                ptr++;
        if(*ptr == '\0') //deals with unexpected/strange case 1
                return "";
        first = ptr - argument;
        
        //find last non-whitespace character:
        
        int last = strlen(argument) - 1;
        ptr = argument + last;
        while(ptr >= argument && isspace(*ptr))
                ptr--;
        last = ptr - argument;
        
        //generate the result string:
        int i;
        for(i = first;i <=last; i++) //case 2 and 3 are handled fine in this loop
                *(result + i - first) = *(argument + i);
        
        *(result + i - first) = '\0';
        return result;
}
                
// Allocate:
// #########            
int allocate(int bytes,int pid,int text_size,int data_size,int heap_size) {
        return 1;
}

// Deallocate:
// ###########
int deallocate(int pid) {
        return 1;
}

//edited by Andrew Pomnean
// Print Memory State:
// ###################
void printMemoryState() {
        // SEGMENTATION Example: 
  // Memory size = 1024 bytes, allocated bytes = 179, free = 845
  // There are currently 10 holes and 3 active process
  // Hole list:
  // hole 1: start location = 0, size = 202
  // ...
  // Process list:
  // process  id=34, size=95 allocation=95
  //    text start=202, size=25
  //    data start=356, size=16
  //    heap start=587, size=54
  // process id=39, size=55 allocation=65
  // ...
  // Failed allocations (No memory) = 2
  //
  
  //if segmentation
  printf("Memory Size = %i bytes, allocated bytes = %i, free = %i \n");
  printf("There are currently %i holes and %i active process(s).\n");
  printf("Hole list: \n");
  printf("...\n");
  printf("Process list: \n");
  //--loop
  printf("process  id=%i,  size=%i,  allocation=%i\n");
  printf("    text start=%i, size=%i\n");
  printf("    data start=%i, size=%i\n");
  printf("    heap start=%i, size=%i\n");
  printf("...\n");
  printf("Failed allocations (No memory) = %i");





  // PAGING Example:
  // Memory size = 1024 bytes, total pages = 32 
  // allocated pages = 6, free pages = 26
  // There are currently 3 active process
  // Free Page list:
  //   2, 6, 7, 8, 9, 10, 11, 12...
  // Process list:
  //  Process id=34, size=95 bytes, number of pages=3
  //    Virt Page 0 -> Phys Page 0  used: 32 bytes
  //    Virt Page 1 -> Phys Page 3  used: 32 bytes
  //    Virt Page 2 -> Phys Page 4  used: 31 bytes
  //  Process id=39, size=55 bytes, number of pages=2
  //    Virt Page 0 -> Phys Page 1  used: 32 bytes
  //    Virt Page 1 -> Phys Page 13  used: 23 bytes
  //  Process id=46, size=29 bytes, number of pages=1
  //    Virt Page 0 -> Phys Page 5  used: 29 bytes
  //
  // Failed allocations (No memory) = 2
  
  //if paging
  printf("Memory Size = %i bytes, allocated bytes = %i, free = %i \n");
  printf("allocated pages = %i, free pages = %i\n");
  printf("There are currently %i holes and %i active process(s).\n");
  printf("Free Page list:\n");
  //--loop
  printf("   %i");
  
  printf("...");
  printf("Process list:\n");
  //--loop
  printf(" process id=%i, size=%i bytes, number of pages=%i\n");
  printf("   Virt Page %i -> Phys Page %i used: %i bytes\n");

  printf("\n");
  printf("Failed allocations (No memory) = %i");
        ;
}
